#include "algo_strickle.h"

// 系统头文件
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

// 模块头文件
#include "algo_mgr.h"
#include "util.h"
#include "sensor.h"
#include "gige.h"
#include "modbus-srv.h"
#include "ctrl.h"

// 第三方头文件
#include "adlist.h" 
#include "Include/AuxLibs/Json/json.h"
#include "Include/AuxLibs/Json/value.h"
#include "Include/INetSdk.h" //  这个头文件包含了json格式取值的函数封装 如GetCgiJSValue_UInt。以后用cjson替代。
#include "Include/IAlarmSdk.h" //notify接口，通知设备抓图，截视频

// 算法库头文件
#include "strickle/MT_Strickle_Alg.h"


typedef Json::FastWriter CConfigWriter;
typedef Json::Reader CConfigReader;
    

#define SS_NORMAL (0) // 正常
#define SS_STOP (1)   // 停车 
#define SS_SUNDRIES (2) // 杂物
#define SS_WARN_INCLINED (4) // 拉斜预警
#define SS_ALARM_INCLINED (8) // 拉斜报警
#define SS_LIGHT_EXCEPTION (16) // 光源异常

// 刮板参数
typedef struct strickle_param {
    struct MT_STRICKLE_ALG_PARAM soparam; //库参数
    int list_size; //队列长度，累计帧数，用来分析
    int incline_warn_sens; // 拉斜预警灵敏度
    int incline_alarm_sens;  // 拉斜报警灵敏度
    int sundries_sens; // 杂物灵敏度
    int light_exception_sens; // 光源异常检测灵敏度（越高越容易检出光源异常）
    uint32_t alarm_action_keep_time; // 检测到报警持续什么的时间？ 截取视频的时长至少大于这个时间，声光报警时长也得大于这个时间
    double light_exception_time; // 光源连续出现问题多久报警（单位：h）
    int frames_from_stop; // 当前帧距收到停车信号时那一帧的帧数
    int frames_after_stop; // 停车延迟帧数
} strickle_param;

static struct last_alarm{
    int alarm_type;        // 上次报警类型
    int frames_up_to_now;  // 帧计数器（上次发生的报警帧距当前帧数）
} last_alarm;

static list* strickle_list = NULL; // 单帧结果链表
static strickle_param* g_param = NULL; // 刮板参数，生命周期等于刮板算法声明周期

/**
 * @brief 开启和关闭硬件只能执行一次
 * 所以维护一个硬件状态,动作执行前判断，执行后更新
 */
struct hw_state {
    int soundlight; //红灯灯状态+声音 1开 0关 默认关
    int yellowlight; // 黄灯 1开 0关 默认关
    int camlight; //相机光源 1开 0关 默认开
} hws;

void _parse_roi_rect(Json::Value& table, MT_STRICKLE_RECT* rect, int channel)
{
    int pts[4][8][2] = {0};
    int region_num = 0;
    GetCgiJSValue_Int(table,"RegionNum",region_num);

    if (region_num == 0) {
        rect->x = 210;
	    rect->y = 60;
	    rect->w = 870;
	    rect->h = 780;
        __log_mesg("Use default rect[%d,%d,%d,%d]\n", rect->x, rect->y, rect->w, rect->h);
        return;
    } 

    int minx = 8192, maxx = 0, miny = 8192, maxy = 0,rect_num = 0;;
    for (int i = 0; i < region_num; i++) {
        GetCgiJSValue_Int(table["Region"][i],"RectNum",rect_num);
        for (int j=0; j<rect_num; j++) {
            GetCgiJSValue_Int(table["Region"][i]["RectInfo"][j],"x",pts[i][j][0]);
            GetCgiJSValue_Int(table["Region"][i]["RectInfo"][j],"y",pts[i][j][1]);
            if (pts[i][j][0] < minx) {
                minx = pts[i][j][0];
            } else if (pts[i][j][0] > maxx) {
                maxx = pts[i][j][0];
            }

            if (pts[i][j][1] < miny) {
                miny = pts[i][j][1];
            } else if (pts[i][j][1] > maxy) {
                maxy = pts[i][j][1];
            }
        }
    }
    // 小窗口坐标和实际坐标转换
    int w = 0, h = 0;
    if (channel == 0) {
        w = sensor_get_width();
        h = sensor_get_height();
    } else if (channel == 1) {
        w = gige_get_width();
        h = gige_get_height();
    } 
    rect->x = ((float)minx/8192.0)*w;
    rect->y = ((float)miny/8192.0)*h;
    rect->w = ((float)(maxx-minx)/8192.0)*w;
    rect->h = ((float)(maxy-miny)/8192.0)*h;
    __log_mesg("Use select rect[%d,%d,%d,%d]\n", rect->x, rect->y, rect->w, rect->h);
}

/**
 * @brief 解析从web传来的json格式刮板配置数据
 * 
 * @param data 数据
 * @param channel 通道号，代表了第几个json对象
 * @return strickle_param* 
 */
static strickle_param* _parse_param(char* data, int channel)
{
    Json::Value table, ctable;
    CConfigReader JsonReader;
    if (JsonReader.parse(data, ctable) == false)
    {
        __log_mesg("parse strickle param error.\n");
        return NULL;
    }
    table = ctable["AlarmCfg"][channel];

    strickle_param* param = (strickle_param*)malloc(sizeof(*param));
    GetCgiJSValue_UInt(table,"sens1", param->soparam.sys_sens1);
    GetCgiJSValue_UInt(table,"sens2", param->soparam.sys_sens2);
    // todo 
    // 类型不匹配，中转一下。膈应。
    double initalangle = 0;
    GetCgiJSValue_Double(table,"initalangle",initalangle);
    param->soparam.inital_angle = initalangle;

    double widthlth = 0;
    GetCgiJSValue_Double(table,"widthlth",widthlth);
    param->soparam.width_lth = widthlth;

    double widthhth = 0;
    GetCgiJSValue_Double(table,"widthhth",widthhth);
    param->soparam.width_hth = widthhth;

    double lenratio = 0;
    GetCgiJSValue_Double(table,"lenratio",lenratio);
    param->soparam.height_lth = lenratio;

    _parse_roi_rect(table, &(param->soparam.rect), channel);

    param->soparam.downsample = 4;

    //int sensitivity = 0; // 2高，1中，0低
    int frame_size = 0;
    GetCgiJSValue_Int(table, "frame", frame_size);
    if (frame_size == 0) { 
        param->list_size  = 25;// 灵敏度高
        __log_mesg("Sensitivity ==> high\n");
    } else if (frame_size < 50) { 
        param->list_size  = 50;// 灵敏度中
        __log_mesg("Sensitivity ==> middle\n");
    } else {                        
        param->list_size  = 100;// 灵敏度低
        __log_mesg("Sensitivity ==> low\n");
    }
    
    //double inclinesens = 0;
    //GetCgiJSValue_Double(table,"inclinesens",inclinesens);
    //double sundriessens = 0;
    //GetCgiJSValue_Double(table,"sundriessens",sundriessens);
    //GetCgiJSValue_UInt(table,"alarmdelay",param->alarm_action_keep_time);

    param->alarm_action_keep_time = 30; // 30秒
    param->incline_alarm_sens = 0.7*param->list_size; // 帧数
    param->incline_warn_sens = 0.5*param->list_size; // 帧数
    param->sundries_sens = 0.7*param->list_size; // 帧数
    param->light_exception_sens = 0.7*param->list_size; // 帧数
    param->light_exception_time = 0.5;// 半个小时
    param->frames_after_stop = 30*25; // 30秒
    param->frames_from_stop = 0;  
    return param;
}

int init_strickle(void* arg)
{
    algo_info* algo = (algo_info*)arg;
    char file[256];
    sprintf(file, "%s/%s", MTPath, algo->name);
    printf("str:parse [%s:%d=>%d].\n", file,algo->id,algo->channel);

    char* data = __read_file(file);
    if (data == NULL) return 1;

    g_param = _parse_param(data, algo->channel);
   

    hws.camlight = 1;
    hws.soundlight = 0;
    hws.yellowlight = 0;

    if (g_param == NULL) return 1;

    MT_Strickle_Alg_Init(g_param->soparam); //调用刮板接口函数初始化
    strickle_list = listCreate(); //创建单帧结果链表 
    listSetFreeMethod(strickle_list, free); 
    memset(&last_alarm, 0x00, sizeof(last_alarm));
    free(data);
    return 0;
}

void* compute_strickle(void* algo, unsigned char* data, int width, int height)
{
    // 停车逻辑:收到停车信号计数，累计30*25帧后，关闭光源，设置输出。停止算法
    if (request_stop_signal()) {
        //__log_mesg("dbg\n");
        if ((g_param->frames_from_stop) >= g_param->frames_after_stop) {
            if (hws.camlight == 1) {
                ctrl_turn_off_camlight();
                hws.camlight = 0;
            }

            if (hws.soundlight == 1) {
                ctrl_turn_off_soundlight();
                hws.soundlight = 0;
            }

            if (hws.yellowlight == 1) {
                ctrl_turn_off_yellowlight();
                hws.yellowlight = 0;
            }

            listEmpty(strickle_list);
            set_input_register_uint16(SS_STOP, TIE_STRICKLE_STATUS_ADDR);
            return NULL;
        }
        ++g_param->frames_from_stop;
    } else {
        if (hws.camlight == 0) {
            ctrl_turn_on_camlight();
            hws.camlight = 1;
        }
        
        g_param->frames_from_stop = 0;
    }
    
    MT_STRICKLE_RESULT* result = (MT_STRICKLE_RESULT*)malloc(sizeof(*result));

    algo_info* algoinfo = (algo_info*)algo;
    static int mock_index = -1;
    if (algoinfo->mock_data) {
        int* data = (int*)algoinfo->mock_data;
        if (++mock_index == algoinfo->mock_len) {
            mock_index = 0;
        }
        result->status = data[mock_index];
        result->angle = 2;
        result->reserved = 0;    
    } else {
        ///__log_mesg("dbg1.\n");
        struct MT_STRICKLE_INFO info;
        MT_Strickle_Alg_GetFrameInfo(&info, data, width, height);
        //__log_mesg("dbg2.\n");
        *result = MT_Strickle_Alg_GetFrameResult(info);
    }

    
    return result;
}

void debug_strickle(unsigned char* data, int width, int height)
{
    printf("str: debug\n");
}

//TODO:帧率是固定参数25帧，要改为动态参数
int _analysis_strickle()
{
    int res = SS_NORMAL;
    static int light_excep_cnt = 0; // 历史光源异常帧数。当检测到光源无异常，清零
    int light_bug_num = 0; // 链表中光源问题帧数
    int inclined_num = 0; // 链表中拉斜帧数
    int sundries_num = 0; // 链表中杂物帧数

    listIter *iter = listGetIterator(strickle_list, AL_START_HEAD);
    listNode* node = NULL;
    while((node = listNext(iter))) {
        MT_STRICKLE_RESULT* result = (MT_STRICKLE_RESULT*)node->value;
        if (result->status == 0) continue;
        switch (result->status)
        {
        case 2://杂物
            sundries_num++;
            break;
        case 3://拉斜
            (result->angle < 6)? (inclined_num++) : (inclined_num += 3);
            break;
        case 16://光源异常
            light_bug_num++;
            break;
        case 19://拉斜+光源异常
            light_bug_num++;
            (result->angle < 6)? inclined_num++:inclined_num += 3;
            break;
        default:
            __log_mesg("Unkown strickle result(%d)\n", result->status);
            break;
        }
    }

    if (inclined_num > g_param->incline_alarm_sens) {
        res |= SS_ALARM_INCLINED;
    } else if (inclined_num > g_param->incline_warn_sens) {
        res |= SS_WARN_INCLINED;
    } else if (sundries_num > g_param->sundries_sens) {
        res |= SS_SUNDRIES;
    }

    //double light_excep_th = strickle_list->len * (1- g_param->light_exception_sens); // 光源异常阈值
    int light_excep_frame_th = g_param->light_exception_time * 3600 * 25;// 光源异常持续时间,小时转换成帧数，一秒25帧
    if (light_bug_num > g_param->light_exception_sens) { 
        if (++light_excep_cnt > light_excep_frame_th) {
            res |= SS_LIGHT_EXCEPTION;
        }
    } else {
        light_excep_cnt = 0;
    }
    return res;
}

void* analysis_strickle(void* input)
{
    int state = -1; // -1表示本次没有结果

    // Assert input is MT_STRICKLE_RESULT type  
    listAddNodeTail(strickle_list, input);
    if (listLength(strickle_list) < g_param->list_size) {
        return state;
    }

    state = _analysis_strickle();
    //__log_mesg("analysis_strickle result: %d\n", state);

    listDelNode(strickle_list, strickle_list->head);     
    return (void*)state;
}

static void _sound_light_down()
{
    printf("Close sound-light.\n ");
}

static void _sound_light_up()
{
    printf("Open sound-light.\n ");
}

int response_strickle(void* arg, void* input) 
{
    int state = (int)input;

    // 程序第一次启动，分析队列还没满。
    // 初始化last_alarm,设置输出为正常。
    if (state == -1) {
        memset(&last_alarm, 0x00, sizeof(last_alarm));
        set_input_register_uint16(SS_NORMAL, TIE_STRICKLE_STATUS_ADDR);
        return 0;
    }

    if (last_alarm.alarm_type != SS_NORMAL) ++last_alarm.frames_up_to_now;

    // 系统类报警目前包括相机断线和光源异常,出现系统类报警，输出该类报警，直到计数器计完一个周期。
    int light_excep_frame_th = g_param->light_exception_time * 3600 * 25;
    if((last_alarm.alarm_type & SS_LIGHT_EXCEPTION) && 
        last_alarm.frames_up_to_now < light_excep_frame_th) {
        return 0;
    }

    if (state & SS_LIGHT_EXCEPTION) {
        __log_mesg("light_exception alarm. reset the counter\n");

        // 屏蔽其他报警的联动
        last_alarm.frames_up_to_now = 0;
        last_alarm.alarm_type = SS_LIGHT_EXCEPTION;
        if (hws.soundlight == 1) {
            ctrl_turn_off_soundlight();// 关闭红灯和声音
            hws.soundlight = 0;
        }

        if (hws.soundlight == 1) {
            ctrl_turn_off_yellowlight();// 关闭黄灯
            hws.yellowlight = 0;
        }

        //_setval(SS_LIGHT_EXCEPTION);
        set_input_register_uint16(SS_LIGHT_EXCEPTION, TIE_STRICKLE_STATUS_ADDR);
        return 0;
    }

    // 历史有报警

    // 停车逻辑。 收到停车信号，启动计时器，计时器到时间后，执行逻辑
    // 逻辑为：输出停车信号，关闭光源，停止声光

    // 本次结果检测正常
    if (state == SS_NORMAL) {
        if (
        ((last_alarm.alarm_type & SS_LIGHT_EXCEPTION) && 
        last_alarm.frames_up_to_now == (light_excep_frame_th + 1)) ||
        ((last_alarm.alarm_type!=SS_NORMAL) && 
        (last_alarm.alarm_type | ~SS_LIGHT_EXCEPTION) &&
        (last_alarm.frames_up_to_now == (g_param->alarm_action_keep_time*25+1)))
        )
         {
            set_input_register_uint16(SS_NORMAL, TIE_STRICKLE_STATUS_ADDR);
            //__log_mesg("Set %d\n",SS_NORMAL);
            //sleep(2);
            set_input_register_uint16(20, TIE_STRICKLE_STATUS_ADDR);
            //__log_mesg("Set 20\n");
            //_setval(state);
            memset(&last_alarm, 0x00, sizeof(last_alarm));
            if (hws.soundlight == 1) {
                ctrl_turn_off_soundlight();// 关闭红灯和声音
                hws.soundlight = 0;
            }
            if (hws.yellowlight == 1) {
                ctrl_turn_off_yellowlight();// 关闭黄灯
                hws.yellowlight = 0;
            }
        }
        return 0;
    }

    // 本次检测结果报警
    if (state >= last_alarm.alarm_type) {
        last_alarm.alarm_type = state;
        last_alarm.frames_up_to_now = 0;
        //_setval(state);
        set_input_register_uint16(state, TIE_STRICKLE_STATUS_ADDR);
        //__log_mesg("Set %d\n",state);
        //sleep(2);
        set_input_register_uint16(20, TIE_STRICKLE_STATUS_ADDR);
        //sleep(2);
        //__log_mesg("Set 20\n");
        if (hws.soundlight == 0 && state == SS_ALARM_INCLINED) {
            ctrl_turn_on_soundlight(); // 开启声音和红灯
            hws.soundlight = 1;
        }

        if (hws.yellowlight == 0 && (state == SS_WARN_INCLINED || state == SS_SUNDRIES)) {
            ctrl_turn_on_yellowlight(); // 开启黄灯
            hws.yellowlight = 1;
        }
    } 
    return 0;
}

void destroy_strickle(void* arg)
{
    printf("+++str: destroy.\n");
    algo_info* algo = (algo_info*)arg;
    if (strickle_list) listRelease(strickle_list);
    memset(&last_alarm, 0x00, sizeof(last_alarm));
    if (g_param) {
        free(g_param);
        g_param = NULL;
    }
    algo->channel = -1;
}


#define TEST_ALGO_STRICKLE 0

#if TEST_ALGO_STRICKLE 
#include "sds.h"
#include <unistd.h>
#include <string.h>

int* _parse_seq(char* buf, int* length) {
    int count;
    sds * splits = sdssplitlen(buf, strlen(buf), "-", 1, &count);
    if (count < 2) return NULL;
    *length = atoi(splits[0]);
    int* seq = (int*)malloc(sizeof(int) * (*length));
    int last_len = 0;
    for (int i=1; i<count; i=i+2) {
        int len = atoi(splits[i]);
        int value = atoi(splits[i+1]);
        for (int j=0; j<len; j++) {
            seq[j + last_len] = value;
        }
        last_len += len;
    }
    sdsfreesplitres(splits, count);
    return seq;
}

#define SS_NORMAL (0) // 正常
#define SS_STOP (1)   // 停车 
#define SS_SUNDRIES (2) // 杂物
#define SS_WARN_INCLINED (4) // 拉斜预警
#define SS_ALARM_INCLINED (8) // 拉斜报警
#define SS_LIGHT_EXCEPTION (16) // 光源异常

int main()
{
    printf(" 0 stands for normal\n"
           " 2 stands for sundries-alarm\n"
           " 4 stands for inclined-warn\n"
           " 8 stands for inclined-alarm\n"
           " 16 stands for light exception\n"
           " 24 stands for incliend and light exception\n"
    );

    g_param = (struct strickle_param*)malloc(sizeof(*g_param));

    printf("Input parameter:");
    char param_desp[6][256] = {{"fps"}, //25
                                {"alarm_action_keep_time"},//30s
                                {"light_exception_time"}};//0.01hour=36s

    int fps = 0;
    printf("Input fps %s: ",param_desp[0]);
    scanf("%d", &fps);
    printf("\n");
    if (fps == 0) fps = 25;
    int sleep_time = 1000/fps;


    int keep_time = 0;
    printf("Input %s: ",param_desp[1]);
    scanf("%d", &keep_time);
    printf("\n");
    if (keep_time == 0) keep_time = 30;
    g_param->alarm_action_keep_time = keep_time;

    float light_fault_time = 0;
    printf("Input %s: ",param_desp[2]);
    scanf("%f", &light_fault_time);
    printf("\n");
    if (light_fault_time == 0) light_fault_time = 0.01;
    g_param->light_exception_time = light_fault_time;

    
    printf("Param: %s=>%d %s=>%d %s=>%.2f\n",param_desp[0], fps, 
            param_desp[1],g_param->alarm_action_keep_time,
            param_desp[2],g_param->light_exception_time);


    printf("Input the frame result seq [total]-[n1]-[s1]-[n2]-[s2]....:\n");
    char buf2[256] = {0};
    scanf("%s", buf2);// scanf遇见空格就结束了?
    int length = 0;
    int* seq = _parse_seq(buf2, &length);
    
    memset(&last_alarm, 0x00, sizeof(last_alarm));
    for (int i=0; i<length; i++)
    {
        gframes = i;
        response_strickle(NULL, (void*)seq[i]);
        usleep(sleep_time*1000);
    }

    free(seq);
    free(g_param);    
    printf("Test done.\n");
    getchar();
}



#endif